package com.liang.blog.auth.service;

import cn.hutool.core.util.ObjectUtil;
import com.liang.blog.Enum.user.UserStatusEnum;
import com.liang.blog.auth.utils.EmailSendUtil;
import com.liang.blog.client.user.UserFeignClient;
import com.liang.blog.common.exception.BlogException;
import com.liang.blog.common.result.Result;
import com.liang.blog.common.result.ResultCodeEnum;
import com.liang.blog.model.BlogUser;
import com.liang.blog.model.constant.CacheConstants;
import com.liang.blog.model.constant.SecurityConstants;
import com.liang.blog.redis.service.RedisService;
import com.liang.blog.security.jwt.JwtHelper;
import com.liang.blog.security.service.TokenService;
import com.liang.blog.security.utils.BlogSecurityUtil;
import com.liang.blog.security.utils.verifyCodeUtil;
import com.liang.blog.vo.user.LoginUserVo;
import com.liang.blog.vo.user.LoginVo;
import com.liang.blog.vo.user.RegisterVo;
import com.liang.blog.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import javax.imageio.ImageIO;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

/**
 * @Author：YanWenLiang
 * @Date:2023-08-17-21:22
 */
@Component
@Slf4j
public class AuthService {

    @Autowired
    private RedisService redisService;

    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserFeignClient userFeignClient;

    @Autowired
    private EmailSendUtil emailSendUtil;

    /**
     * 登入
     *
     * @param loginVo
     * @return
     */
    public Map<String, String> login(LoginVo loginVo) {
        // 判断用户密码是否为空
        if (StringUtils.isEmpty(loginVo.getAccount()) || StringUtils.isEmpty(loginVo.getPassword())) {
            throw new BlogException(ResultCodeEnum.ACCOUNT_PASSWORD_NULL);
        }

        String verifyCode = (String) redisService.getCacheObject(CacheConstants.SYS_VERIFY_CODE_KEY + loginVo.getUuid());

        if (StringUtils.isEmpty(verifyCode)) {
            throw new BlogException(ResultCodeEnum.CODE_EXPIRED);
        }

        if (!verifyCode.toLowerCase().equals(loginVo.getVerifyCode().toLowerCase())) {
            redisService.deleteObject(CacheConstants.SYS_VERIFY_CODE_KEY + loginVo.getUuid());
            throw new BlogException(ResultCodeEnum.CODE_ERROR);
        }

        return this.verifyUserInfo(loginVo.getAccount(), loginVo.getPassword());
    }

    private Map<String, String> verifyUserInfo(String account, String password){
        try {
            Subject subject = SecurityUtils.getSubject();
            UsernamePasswordToken usernamePasswordToken =
                    new UsernamePasswordToken(account, password);
            subject.login(usernamePasswordToken);
            LoginUserVo loginUserVo = (LoginUserVo) subject.getSession().getAttribute("user");

            return this.createToken(loginUserVo);
        } catch (UnknownAccountException e) {
            throw new BlogException(ResultCodeEnum.ACCOUNT_PASSWORD_ERROR);
        } catch (IncorrectCredentialsException e) {
            throw new BlogException(ResultCodeEnum.ACCOUNT_PASSWORD_ERROR);
        }
    }

    public Map<String, String> createToken(LoginUserVo loginUserVo) {
        BlogUser blogUser = loginUserVo.getBlogUser();
        if (UserStatusEnum.DISABLE.getCode() == blogUser.getStatus()) {
            throw new BlogException(ResultCodeEnum.LOGIN_DISABLED_ERROR);
        }

        UUID uuid = UUID.randomUUID();
        loginUserVo.setToken(uuid.toString());
        loginUserVo.setUserid(blogUser.getId());
        loginUserVo.setUsername(blogUser.getNickName());

        Map<String, String> map = new HashMap<>();
        String token = JwtHelper.createToken(blogUser.getId(), blogUser.getAccount(), loginUserVo.getToken());
        map.put("token", token);
        map.put("account", blogUser.getAccount());

        // 添加缓存
        tokenService.updateToken(loginUserVo);
        return map;
    }

    /**
     * 邮箱登入
     * @param loginVo
     * @return
     */
    public Map<String, String> emailLogin(LoginVo loginVo){
        // 判断用户密码是否为空
        if (StringUtils.isEmpty(loginVo.getEmail()) || StringUtils.isEmpty(loginVo.getVerifyCode())) {
            throw new BlogException("请输入邮箱和验证码！");
        }

        Integer verifyCode = redisService.getCacheObject(CacheConstants.SYS_LOGIN_VERIFY_CODE_KEY + loginVo.getUuid());
        if(StringUtils.isEmpty(verifyCode.toString())){
            throw new BlogException(ResultCodeEnum.CODE_EXPIRED);
        }

        if(!verifyCode.toString().equals(loginVo.getVerifyCode())){
            throw new BlogException(ResultCodeEnum.CODE_ERROR);
        }

        Result<BlogUser> userByEmail = userFeignClient.getUserByEmail(loginVo.getEmail());
        BlogUser blogUser = userByEmail.getData();

        if (ObjectUtils.isEmpty(blogUser)){
            throw new BlogException("用户不存在，请先登入！");
        }

        LoginUserVo loginUserVo = new  LoginUserVo();
        loginUserVo.setBlogUser(blogUser);
        Map<String, String> map = this.createToken(loginUserVo);
        redisService.deleteObject(CacheConstants.SYS_LOGIN_VERIFY_CODE_KEY + loginVo.getUuid());
        return map;
    }

    /**
     * 根据 token 获取用户
     *
     * @param token
     * @return
     */
    public LoginUserVo getUserInfoVoByToken(String token) {
        String userKey = JwtHelper.getUserKey(token);
        LoginUserVo loginUserVo = redisService.getCacheObject(CacheConstants.LOGIN_TOKEN_KEY + userKey);
        if (loginUserVo != null) {
            return loginUserVo;
        }
        throw new BlogException(ResultCodeEnum.FETCH_USERINFO_ERROR);
    }


    public static void main(String[] args) {

        // 创建md5，使用Md5Hash构造方法进行加密
        Md5Hash md5Hash = new Md5Hash("123456");
        System.out.println("md5 = " + md5Hash);
        // md5 + salt
        Md5Hash md5Hash1 = new Md5Hash("123", "zard");
        System.out.println("md5 + salt = " + md5Hash1);
        // md5 + salt + hash散列次数
        Md5Hash md5Hash2 = new Md5Hash("123456", "2023-08-18/08:42:17", 1024);
        System.out.println("md5 + salt + hash散列次数 = " + md5Hash2);
    }


    /**
     * 绘制验证码
     *
     * @param UUID
     * @param response
     */
    public void getVerifyCode(String UUID, HttpServletResponse response) {
        // 设置响应头信息
        response.setContentType("image/jpeg");
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);

        // 生成验证码图片
        verifyCodeUtil captcha = new verifyCodeUtil(100, 50, 4, 10);
        String code = captcha.getCode();

        // 设置验证码缓存
        redisService.setCacheObject(CacheConstants.SYS_VERIFY_CODE_KEY + UUID, code, 1L, TimeUnit.MINUTES);

        // 将验证码图片返回给前端
        try {
            ServletOutputStream out = response.getOutputStream();
            ImageIO.write(captcha.generateCaptchaImage(code), "jpeg", out);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 注册
     *
     * @param registerVo
     * @return
     */
    public Boolean register(RegisterVo registerVo) {
        if (!ObjectUtil.isAllNotEmpty(registerVo.getAccount(), registerVo.getPassword(), registerVo.getEmail(), registerVo.getVerifyCode())) {
            throw new BlogException(ResultCodeEnum.PARAM_ERROR);
        }

        Integer verifyCode = redisService.getCacheObject(CacheConstants.SYS_REGISTER_VERIFY_CODE_KEY + registerVo.getUuid());
        if (StringUtils.isEmpty(verifyCode)) {
            throw new BlogException(ResultCodeEnum.CODE_EXPIRED);
        }

        if (verifyCode.intValue() != Integer.parseInt(registerVo.getVerifyCode())) {
            throw new BlogException(ResultCodeEnum.CODE_ERROR);
        }

        Result<Integer> count = userFeignClient.getUserCount(registerVo.getAccount());

        if (!ObjectUtils.isEmpty(count.getData()) && count.getData() > 0) {
            throw new BlogException("用户存在！");
        }

        // 默认头像
        registerVo.setAvatar("https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png");
        registerVo.setCreateTime(new Date());
        registerVo.setNickName("用户_" + registerVo.getAccount());
        registerVo.setStatus(UserStatusEnum.ENABLE.getCode());
        registerVo.setRoleId(4);

        String format = new SimpleDateFormat("yyyy-MM-dd/HH:mm:ss").format(registerVo.getCreateTime());
        Md5Hash md5Hash = new Md5Hash(registerVo.getPassword(), format, 1024);
        System.out.println(md5Hash.toHex());
        registerVo.setPassword(md5Hash.toHex());
        Result<Boolean> booleanResult = userFeignClient.saveUser(registerVo);
        return booleanResult.getData();
    }

//    @Async
//    public CompletableFuture<Boolean> registerEmail(String email, String uuid){
//        try {
//            int random = this.createRandom();
//            Boolean flag = this.sendEmail(email, uuid, "绫三博客注册验证码",
//                    "欢迎您成为社区的一员，您的注册验证码为: " + random + ",请在5分钟内使用！", random);
//            log.info("邮件发送成功");
//            return CompletableFuture.completedFuture(flag);
//        } catch (Exception e) {
//            log.info("邮件发送失败");
//            return CompletableFuture.completedFuture(false);
//        }
//    }

    public Boolean loginEmail(String email, String uuid) {
        int random = this.createRandom();
        return this.sendEmail(email, CacheConstants.SYS_LOGIN_VERIFY_CODE_KEY + uuid, "绫三博客登入验证码",
                "亲爱的同学：\n\n您好！欢迎您成为我们社区的一员。\n\n您的登入验证码为: " + random + ", 请在5分钟内使用！\n\n祝您使用愉快！", random);
    }

    public Boolean registerEmail(String email, String uuid) {
        Result<Integer> userByEmailCount = userFeignClient.getUserByEmailCount(email);

        if (!ObjectUtils.isEmpty(userByEmailCount.getData()) && userByEmailCount.getData() > 0) {
            throw new BlogException("邮箱已存在！");
        }
        int random = this.createRandom();
        return this.sendEmail(email, CacheConstants.SYS_REGISTER_VERIFY_CODE_KEY + uuid, "绫三博客注册验证码",
                "亲爱的同学：\n\n您好！欢迎您成为我们社区的一员。\n\n您的注册验证码为: " + random + ", 请在5分钟内使用！\n\n祝您使用愉快！", random);
    }


    private Boolean sendEmail(String email, String key, String subject, String text, int random) {
        if (!email.matches("^[1-9]\\d{4,10}@qq\\.com$")) {
            throw new BlogException("请输入正确的qq邮箱！");
        }

        emailSendUtil.sendText(subject, text, "社区管理员", email);
        System.out.println("验证码为：" + random);
        // 设置验证码缓存
        redisService.setCacheObject(key, random, 5L, TimeUnit.MINUTES);

        return true;
    }

    private int createRandom() {
        Random random = new Random();
        int min = 100000; // 最小值为100000
        int max = 999999; // 最大值为999999
        return random.nextInt(max - min + 1) + min;
    }
}
